#include "includes.h"

/******************************************************************************
 Function Prototypes
 ******************************************************************************/
void setupScene();

// Callbacks
void display();
void reshape(int,int);
void keyboard(unsigned char,int,int);
void SpecialKeys(int, int, int);

///////////////////////////////////////////////////////////////////////////////

/******************************************************************************
 Global Variables
 ******************************************************************************/
float deflection = 0, elevation = -16.875, camD = 0, camE = 0;
int spin = 0;
bool follow = false, freeCam = false;

GLfloat	keyLightPos[] = { -20.0f, 30.0f, 35.0f, 1.0f };
GLfloat keySpotLightDir[] = { 30.0f, -30.0f, -25.0f};

GLfloat	fillLightPos[] = { 30.0f, 0.0f, -25.0f, 1.0f };
GLfloat fillSpotLightDir[] = { -20.0f, 0.0f, 35.0f};

GLfloat	backLightPos[] = { 0.0f, -3.0f, -12.0f, 1.0f };
GLfloat backSpotLightDir[] = {0.0f, 10.0f, 250.0f};

map<string, Camera*> cameras;
Camera* currentCamera;
Paladin howitzer;
Terrain terrain;
///////////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[])
{
   glutInit(&argc, argv);
   glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
   
   cameras.insert( make_pair("Gun", howitzer.getCamera()) );
   cameras.insert( make_pair("Round", Round::GetCamera()) );
   
   currentCamera = cameras["Gun"];
   
   glutInitWindowSize(800, 600);
   glutCreateWindow("CS490 Final Project - Colin and David");
   
   glutDisplayFunc(display);
   glutIdleFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutSpecialFunc(SpecialKeys);
   setupScene();
   
   glutMainLoop();
}

void setupScene()
{
   // How things are drawn
   glEnable(GL_DEPTH_TEST);
   glFrontFace(GL_CCW);
   glEnable(GL_CULL_FACE);
   
   // Normalize Everything
   glEnable(GL_NORMALIZE);
   
   // Enable lighting
   glEnable(GL_LIGHTING);
   glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
   
   // Light 1 Key light
   {
      // Key Light values and coordinates
      GLfloat  ambientKey[] = { 1.0f, 1.0f, 1.0f, 0.0f };
      GLfloat  diffuseKey[] = { 1.0f, 1.0f, 1.0f, 1.0f };
      GLfloat  specularKey[] = { 1.0f, 1.0f, 1.0f, 1.0f };
      
      // Setup and enable light 0 Fill light
      glLightfv(GL_LIGHT0,GL_AMBIENT,ambientKey);
      glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseKey);
      glLightfv(GL_LIGHT0,GL_SPECULAR,specularKey);

      
      // Used for spot lights
      glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 3);
      glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80);
      glEnable(GL_LIGHT0);
   }
   
   // Light 2 Fill light
   {
      // Fill Light values and coordinates
      GLfloat  ambientFill[] = { 0.5f, 0.5f, 0.5f, 1.0f };
      GLfloat  diffuseFill[] = { 1.0f, 1.0f, 1.0f, 1.0f };
      GLfloat  specularFill[] = { 0.5f, 0.5f, 0.5f, 1.0f };
      
      // Setup and enable Fill light
      glLightfv(GL_LIGHT1,GL_AMBIENT,ambientFill);
      glLightfv(GL_LIGHT1,GL_DIFFUSE,diffuseFill);
      glLightfv(GL_LIGHT1,GL_SPECULAR,specularFill);
      
      // Used for spot lights
      glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 10);
      glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 40);
      glEnable(GL_LIGHT1);
   }
   
   // Light 3 Back Light
   {
      // Back Light values and coordinates
      GLfloat  ambientBack[] = { 1.0f, 1.0f, 1.0f, 1.0f };
      GLfloat  diffuseBack[] = { 0.5f, 0.5f, 0.5f, 0.5f };
      GLfloat  specularBack[] = { 0.75f, 0.75f, 0.75f, 0.75f };
      
      // Setup and enable back light
      glLightfv(GL_LIGHT2,GL_AMBIENT,ambientBack);
      glLightfv(GL_LIGHT2,GL_DIFFUSE,diffuseBack);
      glLightfv(GL_LIGHT2,GL_SPECULAR, specularBack);
      
      // Used for spot lights
      glLightf(GL_LIGHT2, GL_SPOT_EXPONENT, 6);
      glLightf(GL_LIGHT2, GL_SPOT_CUTOFF, 60);
      glEnable(GL_LIGHT2);
   }
}

// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
   if (Round::GetCamera())
      return;

   if(key == GLUT_KEY_F1)
   {
      follow = false;
      Round::AddRound(elevation, deflection, follow);
   }
   else if (key == GLUT_KEY_F2)
   {
      follow = true;
      Round::AddRound(elevation, deflection, follow);
      currentCamera = Round::GetCamera();
   }
   
   // Refresh the Window
   glutPostRedisplay();   
}

void keyboard(unsigned char key, int x, int y)
{
   if (follow)
      if (key == 27) exit(0);
      else return;

   if (freeCam)
   {
      switch (key)
      {
         case 8:  // Backspace
            freeCam = false;
            break;
         case 27: // ESC key exits
            exit(0);
            break;
         case 'w':
            camE += 5;
            howitzer.UpdateCamera(camE, camD);
            break;
         case 's':
            camE -= 5;
            howitzer.UpdateCamera(camE, camD);
            break;
         case 'a':
            camD -= 5;
            howitzer.UpdateCamera(camE, camD);
            break;
         case 'd':
            camD += 5;
            howitzer.UpdateCamera(camE, camD);
            break;
      }
   }
   else
   {
      switch (key)
      {
         case 8:
            freeCam = true;
            break;
         case 27:
            exit(0);
            break;
         case ' ':
            Round::Update();
            break;
         case 'a':
            deflection += 1;
            break;
         case 'd':
            deflection -= 1;
            break;
         case 'w':
            elevation -= 1;
            break;
         case 's':
            elevation += 1;
            break;

      }
   }
   
   // Keep the deflection inside a single circle.
   if (deflection < 0)
      deflection = 360 + deflection;
   else if (deflection > 360)
      deflection -= 360;
   
   // Tube can't go past straight up.
   if (elevation < -30)
      elevation = -30;
   // Tube can't go past flat.
   else if (elevation > -5)
      elevation = -5;
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei)w, (GLsizei)h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(32, (GLfloat)w / (GLfloat)h, 1.0, 1000.0);
   glMatrixMode(GL_MODELVIEW);
}

void display(void)
{
   if (!Round::GetCamera())
   {
      currentCamera = howitzer.getCamera();
      follow = false;
   }

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();
   
   gluLookAt(currentCamera->GetLocation()[x], currentCamera->GetLocation()[y], currentCamera->GetLocation()[z],
             currentCamera->GetDirection()[x], currentCamera->GetDirection()[y], currentCamera->GetDirection()[z],
             0, 1, 0);
   
   // Set the light position before any transforms
   glLightfv(GL_LIGHT0,GL_POSITION,keyLightPos);
   glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, keySpotLightDir);
   
   glLightfv(GL_LIGHT1,GL_POSITION,fillLightPos);
   glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, fillSpotLightDir);
   
   glLightfv(GL_LIGHT2,GL_POSITION,backLightPos);
   glLightfv(GL_LIGHT2, GL_SPOT_DIRECTION, backSpotLightDir);
   
   glPushMatrix();
      terrain.Draw(currentCamera);
   glPopMatrix();
   
   glPushMatrix();
      howitzer.Draw(elevation, deflection, !freeCam);
   glPopMatrix();
   
   Round::DrawRounds();
   
   glutSwapBuffers();
}
